package com.ikingtech.platform.service.system.dept.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ikingtech.framework.sdk.base.model.DragOrderParam;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelper;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelperBuilder;
import com.ikingtech.framework.sdk.department.api.DeptApi;
import com.ikingtech.framework.sdk.department.api.DeptRoleDataScopeApi;
import com.ikingtech.framework.sdk.department.api.DeptUserApi;
import com.ikingtech.framework.sdk.department.model.DeptBasicDTO;
import com.ikingtech.framework.sdk.department.model.DeptDTO;
import com.ikingtech.framework.sdk.department.model.DeptQueryParamDTO;
import com.ikingtech.framework.sdk.enums.common.DragTargetPositionEnum;
import com.ikingtech.framework.sdk.enums.system.department.DeptTypeEnum;
import com.ikingtech.framework.sdk.label.api.LabelApi;
import com.ikingtech.framework.sdk.label.model.LabelAssignDTO;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.dept.entity.DepartmentDO;
import com.ikingtech.platform.service.system.dept.exception.DeptExceptionInfo;
import com.ikingtech.platform.service.system.dept.service.repository.DeptRepository;
import com.ikingtech.platform.service.system.dept.service.repository.ModelConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/dept", name = "系统管理-部门管理", description = "系统管理-部门管理，不支持多个根部门，所有部门均归属于默认根部门，默认根部门编号为root")
public class DeptController implements DeptApi {

    private final DeptRepository repo;

    private final DeptUserApi deptUserApi;

    private final LabelApi labelApi;

    private final DeptRoleDataScopeApi deptRoleDataScopeApi;

    private final ModelConverter converter;

    /**
     * 添加部门
     *
     * @param dept 部门信息
     * @return 返回添加结果
     */
    @Override
    @OperationLog(value = "新增部门", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(DeptDTO dept) {
        if (this.repo.exists(Wrappers.<DepartmentDO>lambdaQuery()
                .and(Tools.Str.isBlank(dept.getParentId()), wrapper -> wrapper.eq(DepartmentDO::getParentId, Tools.Str.EMPTY).or().isNull(DepartmentDO::getParentId))
                .eq(Tools.Str.isNotBlank(dept.getParentId()), DepartmentDO::getParentId, dept.getParentId())
                .eq(DepartmentDO::getName, dept.getName())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DepartmentDO::getTenantCode, Me.tenantCode()))) {
            throw new FrameworkException(DeptExceptionInfo.DUPLICATE_DEPT_NAME);
        }
        // 将DeptDTO对象转换为DepartmentDO对象
        DepartmentDO entity = Tools.Bean.copy(dept, DepartmentDO.class);
        // 生成新的ID
        entity.setId(Tools.Id.uuid());
        // 设置租户代码
        entity.setTenantCode(Me.tenantCode());
        // 构造完整的路径
        DepartmentDO parentEntity = this.repo.getById(entity.getParentId());
        entity.setFullPath(this.parseFullPath(parentEntity, entity.getId()));
        if (DeptTypeEnum.UNITY.equals(dept.getType())) {
            entity.setGrade(null == parentEntity ? 1 : parentEntity.getGrade() + 1);
        } else {
            entity.setGrade(1);
        }
        // 设置排序号
        entity.setSortOrder(this.getMaxSortOrder(entity.getParentId(), Me.tenantCode()) + 1);
        // 分配标签
        this.assignLabel(entity.getId(), dept.getLabelIds());
        // 保存到数据库
        this.repo.save(entity);
        // 返回添加结果
        return R.ok(entity.getId());
    }

    /**
     * 删除部门
     *
     * @param id 部门ID
     * @return 删除结果
     */
    @Override
    @OperationLog(value = "删除部门")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        // 根据ID查询部门实体
        DepartmentDO entity = this.repo.getById(id);
        if (null == entity) {
            return R.ok();
        }
        // 查询所有子部门ID
        List<String> ids = this.repo.listObjs(Wrappers.<DepartmentDO>lambdaQuery().select(DepartmentDO::getId).likeRight(DepartmentDO::getFullPath, entity.getFullPath()));
        // 移动用户到父部门
        this.deptUserApi.moveUser(entity.getParentId(), ids, Me.tenantCode());
        // 移除角色数据范围
        this.deptRoleDataScopeApi.removeRoleDataScopeByDeptIds(ids, Me.tenantCode());
        // 批量删除部门
        this.repo.removeBatchByIds(ids);
        return R.ok();
    }

    /**
     * 更新部门
     *
     * @param dept 部门DTO对象
     * @return 返回操作结果
     */
    @Override
    @OperationLog(value = "更新部门")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(DeptDTO dept) {
        // 检查部门是否存在
        if (!this.repo.exists(Wrappers.<DepartmentDO>lambdaQuery().eq(DepartmentDO::getId, dept.getId()))) {
            throw new FrameworkException(DeptExceptionInfo.DEPT_NOT_FOUND);
        }
        if (this.repo.exists(Wrappers.<DepartmentDO>lambdaQuery()
                .ne(DepartmentDO::getId, dept.getId())
                .and(Tools.Str.isBlank(dept.getParentId()), wrapper -> wrapper.eq(DepartmentDO::getParentId, Tools.Str.EMPTY).or().isNull(DepartmentDO::getParentId))
                .eq(Tools.Str.isNotBlank(dept.getParentId()), DepartmentDO::getParentId, dept.getParentId())
                .eq(DepartmentDO::getName, dept.getName())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), DepartmentDO::getTenantCode, Me.tenantCode()))) {
            throw new FrameworkException(DeptExceptionInfo.DUPLICATE_DEPT_NAME);
        }
        // 将DTO对象转换为DO对象
        DepartmentDO entity = Tools.Bean.copy(dept, DepartmentDO.class);
        // 设置租户代码和完整路径
        entity.setTenantCode(Me.tenantCode());
        entity.setFullPath(this.parseFullPath(this.repo.getById(entity.getParentId()), entity.getId()));
        // 分配标签
        this.assignLabel(entity.getId(), dept.getLabelIds());
        // 更新部门信息
        this.repo.updateById(entity);
        return R.ok();
    }

    /**
     * 分页查询部门信息
     *
     * @param queryParam 查询参数
     * @return 分页结果
     */
    @Override
    public R<List<DeptDTO>> page(DeptQueryParamDTO queryParam) {
        List<String> dataScope = Me.dataScope();
        if (Me.invalidDataScope(dataScope)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(PageResult.build(this.repo.page(new Page<>(queryParam.getPage(), queryParam.getRows()), DeptRepository.createQueryWrapper(queryParam, dataScope, Me.tenantCode()))).convertBatch(this.converter::modelConvert));
    }

    /**
     * 获取所有DeptBasicDTO对象列表
     *
     * @param excludeId 排除的部门ID
     * @return 返回DeptBasicDTO对象列表
     */
    @Override
    public R<List<DeptBasicDTO>> all(String excludeId) {
        // 获取当前用户的数据范围
        List<String> dataScopeCodes = Me.dataScope();
        // 判断数据范围是否有效
        if (Me.invalidDataScope(dataScopeCodes)) {
            return R.ok(new ArrayList<>());
        }
        // 获取排除的部门ID列表
        List<String> excludeDeptIds = new ArrayList<>();
        // 如果排除的部门ID不为空
        if (Tools.Str.isNotBlank(excludeId)) {
            // 根据排除的部门ID获取部门对象
            DepartmentDO entity = this.repo.getById(excludeId);
            // 如果部门对象存在
            if (null != entity) {
                // 获取该部门下的所有部门ID
                excludeDeptIds.addAll(this.repo.listObjs(Wrappers.<DepartmentDO>lambdaQuery().select(DepartmentDO::getId).likeRight(DepartmentDO::getFullPath, entity.getFullPath())));
            }
        }
        // 根据数据范围、排除的部门ID列表和租户代码查询部门对象列表
        return R.ok(this.converter.modelInfoConvert(this.repo.list(Wrappers.<DepartmentDO>lambdaQuery().in(Tools.Coll.isNotBlank(dataScopeCodes), DepartmentDO::getId, dataScopeCodes).notIn(Tools.Coll.isNotBlank(excludeDeptIds), DepartmentDO::getId, excludeDeptIds).eq(DepartmentDO::getTenantCode, Me.tenantCode()).orderByDesc(DepartmentDO::getCreateTime))));
    }

    @Override
    public R<List<DeptBasicDTO>> list(DeptQueryParamDTO queryParam) {
        return R.ok(this.converter.modelInfoConvert(this.repo.list(DeptRepository.createQueryWrapper(queryParam, Me.tenantCode()))));
    }

    /**
     * 获取部门详情
     *
     * @param id 部门ID
     * @return 返回部门详情
     */
    @Override
    public R<DeptDTO> detail(String id) {
        // 根据ID查询部门实体
        DepartmentDO entity = this.repo.getById(id);
        if (null == entity) {
            throw new FrameworkException(DeptExceptionInfo.DEPT_NOT_FOUND);
        }
        // 查询所有子部门ID
        List<String> subEntityIds = this.repo.listObjs(Wrappers.<DepartmentDO>lambdaQuery().select(DepartmentDO::getId).likeRight(DepartmentDO::getFullPath, entity.getFullPath()));
        // 转换部门实体为DTO对象，并获取部门管理员和用户数量
        return R.ok(this.converter.modelConvert(entity, this.deptUserApi.loadManager(entity.getManagerId()), this.deptUserApi.getUserCount(subEntityIds)));
    }

    /**
     * 获取所有子部门的DTO列表
     *
     * @param queryParam 查询参数
     * @return 返回包含子部门DTO列表的响应对象
     */
    @Override
    public R<List<DeptDTO>> listSubAll(DeptQueryParamDTO queryParam) {
        // 获取所有不重复的部门ID
        List<String> deptIds = this.parseQueriedDeptIds(queryParam);
        // 如果部门ID列表为空，则返回空列表
        if (Tools.Coll.isBlank(deptIds)) {
            return R.ok(new ArrayList<>());
        }
        // 调用存储库的listByIds方法获取部门列表，并使用转换器将实体转换为DTO
        return R.ok(this.converter.modelConvert(this.repo.listByIds(deptIds)));
    }

    /**
     * 获取所有子部门的基本信息
     *
     * @param queryParam 查询参数
     * @return 返回包含所有子部门基本信息的列表
     */
    @Override
    public R<List<DeptBasicDTO>> listSubInfoAll(DeptQueryParamDTO queryParam) {
        // 获取所有部门的ID列表
        List<String> deptIds = this.parseQueriedDeptIds(queryParam);
        // 如果部门ID列表为空，则返回空列表
        if (Tools.Coll.isBlank(deptIds)) {
            return R.ok(new ArrayList<>());
        }
        // 调用仓库方法获取部门信息，并转换为DeptBasicDTO对象列表
        return R.ok(this.converter.modelInfoConvert(this.repo.listByIds(deptIds)));
    }

    /**
     * 拖拽操作
     *
     * @param dragParam 拖拽参数
     * @return 操作结果
     */
    @Override
    public R<Object> drag(DragOrderParam dragParam) {
        if (DragTargetPositionEnum.NONE.equals(dragParam.getPosition())) {
            return R.ok();
        }
        // 创建拖拽帮助器
        DragHelper<DepartmentDO> dragHelper = DragHelperBuilder.builder(
                        // 获取当前拖拽对象的上级ID和当前排序
                        () -> this.repo.list(Wrappers.<DepartmentDO>query().lambda()
                                // 父部门id等于parentId
                                .eq(DepartmentDO::getParentId, dragParam.getParentId())
                                // 排序顺序大于value
                                .gt(DepartmentDO::getSortOrder, dragParam.getCurrentOrder())
                                // 按升序排序
                                .orderByAsc(DepartmentDO::getSortOrder)
                        ),
                        // 获取目标拖拽对象的上级ID和目标排序
                        () -> this.repo.list(Wrappers.<DepartmentDO>query().lambda()
                                // 父部门id等于parentId
                                .eq(DepartmentDO::getParentId, dragParam.getTargetParentId())
                                // 排序顺序大于value
                                .gt(DepartmentDO::getSortOrder, dragParam.getTargetOrder())
                                // 按升序排序
                                .orderByAsc(DepartmentDO::getSortOrder)
                        ),
                        // 获取当前拖拽对象的上级ID和目标拖拽对象的上级ID之间的排序
                        (starOrder, endOrder) -> this.repo.list(Wrappers.<DepartmentDO>query().lambda()
                                // 父部门id等于parentId
                                .eq(DepartmentDO::getParentId, dragParam.getParentId())
                                // 排序范围在startValue和endValue之间
                                .between(DepartmentDO::getSortOrder, starOrder, endOrder)
                        )
                )
                // 根据拖拽对象的上级ID和目标拖拽对象的上级ID以及拖拽位置确定拖拽帮助器的类型
                .which(dragParam.getParentId(), dragParam.getTargetParentId(), DragTargetPositionEnum.INNER.equals(dragParam.getPosition()))
                // 获取当前拖拽对象
                .currentNode(() -> this.repo.getById(dragParam.getCurrentId()))
                // 获取目标拖拽对象
                .targetNode(() -> this.repo.getById(dragParam.getTargetId()))
                // 获取目标拖拽对象的上级ID的最大排序
                .maxSortOrder(() -> this.getMaxSortOrder(dragParam.getTargetParentId(), Me.tenantCode()))
                // 根据拖拽位置确定是否在目标拖拽对象之前
                .beforeTarget(DragTargetPositionEnum.BEFORE.equals(dragParam.getPosition()))
                .build();
        // 执行拖拽操作
        this.repo.updateBatchById(dragHelper.drag());
        return R.ok();
    }

    /**
     * 处理租户初始化事件
     *
     * @param event 租户初始化事件对象
     */
    @EventListener
    public void tenantInitEventListener(TenantInitEvent event) {
        // 创建部门实体对象
        DepartmentDO entity = new DepartmentDO();
        // 设置部门ID，格式为租户代码+根部门ID
        entity.setId(Tools.Id.uuid());
        // 设置部门名称
        entity.setName(event.getName());
        // 设置父部门ID为空
        entity.setParentId(Tools.Str.EMPTY);
        // 设置部门完整路径为部门ID
        entity.setFullPath(entity.getId());
        // 设置部门租户代码
        entity.setTenantCode(event.getCode());
        // 设置部门排序顺序为父部门排序顺序+1
        entity.setSortOrder(this.getMaxSortOrder(entity.getParentId(), Me.tenantCode()) + 1);
        // 保存部门实体到数据库
        this.repo.save(entity);
    }

    /**
     * 处理租户删除事件
     *
     * @param event 租户删除事件对象
     */
    @EventListener
    public void tenantDeleteEventListener(TenantDeleteEvent event) {
        // 根据租户代码删除部门数据
        this.repo.remove(Wrappers.<DepartmentDO>lambdaQuery().eq(DepartmentDO::getTenantCode, event.getCode()));
    }

    /**
     * 解析查询的部门ID列表
     *
     * @param queryParam 查询参数
     * @return 解析后的部门ID列表
     */
    private List<String> parseQueriedDeptIds(DeptQueryParamDTO queryParam) {
        // 获取数据范围
        List<String> dataScope = Me.dataScope();
        // 如果数据范围无效
        if (Me.invalidDataScope(dataScope)) {
            // 返回空列表
            return Collections.emptyList();
        }
        // 将部门实体列表转换为部门ID列表
        List<String> queriedDeptIds = new ArrayList<>(dataScope);
        // 如果父部门ID不为空
        if (Tools.Str.isNotBlank(queryParam.getParentDeptId())) {
            // 获取父部门部门的完整路径列
            String fullPath = this.repo.getObj(Wrappers.<DepartmentDO>lambdaQuery().select(DepartmentDO::getFullPath).eq(DepartmentDO::getId, queryParam.getParentDeptId()), String.class::cast);
            if (Tools.Str.isBlank(fullPath)) {
                // 返回空列表
                return Collections.emptyList();
            }
            queriedDeptIds.addAll(this.repo.listObjs(Wrappers.<DepartmentDO>lambdaQuery().select(DepartmentDO::getId).likeRight(DepartmentDO::getFullPath, fullPath)));
            if (Tools.Coll.isBlank(queriedDeptIds)) {
                // 返回空列表
                return Collections.emptyList();
            }
        }
        // 根据查询参数、数据范围和租户代码获取部门实体列表
        List<DepartmentDO> entities = this.repo.list(DeptRepository.createQueryWrapper(queryParam, queriedDeptIds, Me.tenantCode()));
        // 如果部门实体列表为空
        if (Tools.Coll.isBlank(entities)) {
            // 返回空列表
            return Collections.emptyList();
        }
        // 将部门实体列表转换为部门ID列表
        queriedDeptIds.clear();
        queriedDeptIds.addAll(Tools.Coll.convertList(entities, DepartmentDO::getId));
        // 获取上级部门ID列表
        List<String> queriedParentDeptIds = Tools.Coll.flatMap(entities, entity -> Tools.Coll.filter(Tools.Str.split(Tools.Str.isBlank(queryParam.getParentDeptId()) ?
                        entity.getFullPath() :
                        entity.getFullPath().substring(entity.getFullPath().indexOf(queryParam.getParentDeptId())),
                "@"), deptId -> !queriedDeptIds.contains(deptId)), Collection::stream);
        // 如果上级部门ID列表不为空
        if (Tools.Coll.isNotBlank(queriedParentDeptIds)) {
            // 将上级部门ID列表转换为部门ID列表并添加到结果列表中
            queriedDeptIds.addAll(Tools.Coll.convertList(this.repo.listByIds(queriedParentDeptIds), DepartmentDO::getId));
        }
        // 返回解析后的部门ID列表
        return Tools.Coll.distinct(queriedDeptIds);
    }

    /**
     * 解析完整路径
     *
     * @param parentEntity 父组织
     * @param id           当前路径ID
     * @return 完整路径
     */
    private String parseFullPath(DepartmentDO parentEntity, String id) {
        if (null != parentEntity) {
            // 获取父路径实体
            if (Tools.Str.isBlank(parentEntity.getFullPath())) {
                return id;
            }
            return parentEntity.getFullPath() + "@" + id;
        } else {
            return id;
        }
    }

    /**
     * 根据条件查询排序号列表
     *
     * @param parentId   父ID
     * @param tenantCode 租户代码
     * @return 最大的排序号
     */
    private Integer getMaxSortOrder(String parentId, String tenantCode) {
        // 根据条件查询排序号列表
        List<Number> orders = this.repo.listObjs(Wrappers.<DepartmentDO>lambdaQuery()
                .select(DepartmentDO::getSortOrder)
                .eq(DepartmentDO::getParentId, Tools.Str.isBlank(parentId) ? Tools.Str.EMPTY : parentId)
                .eq(Tools.Str.isNotBlank(tenantCode), DepartmentDO::getTenantCode, tenantCode)
                .orderByDesc(DepartmentDO::getSortOrder));
        // 如果排序号列表为空，则返回0，否则返回第一个排序号的整数值
        return Tools.Coll.isBlank(orders) ? 0 : orders.get(0).intValue();
    }

    /**
     * 为部门分配标签
     *
     * @param deptId   部门ID
     * @param labelIds 标签ID列表
     */
    private void assignLabel(String deptId, List<String> labelIds) {
        LabelAssignDTO labelAssign = new LabelAssignDTO();
        labelAssign.setBusinessId(deptId);
        labelAssign.setLabelIds(labelIds);
        this.labelApi.assign(labelAssign);
    }
}
